<!---
	Project:	cfTrigger
	Summary:	System utility library, used to handle general functions
	
	Log:
	
		Created:		05/08/2009		
		Modified:

--->

<cfcomponent displayname="Utils" hint="Handles utility funtions" output="false">

	<cfsetting enablecfoutputonly="yes">


	<!--- --------------------------------------------------------------------------------------- ----
		
		Website: CFlib.org
		
		Author:
		Jordan Clark  
		
		Link:
		http://www.cflib.org/index.cfm?event=page.udfbyid&udfid=812
		
		Date Posted:
		Nov 19, 2002
		
		Purpose: remove white spaces in the output generated by Coldfusion
		
	---- --------------------------------------------------------------------------------------- --->

	<cffunction name="HtmlCompressFormat" access="public" returntype="string" output="false">
		<cfargument name="sInput" type="string" required="yes">
		<cfargument name="level" type="numeric" default="2" required="no">		
		<cfset var result = trim(arguments.sInput)>
		
		<cfswitch expression="#arguments.level#">
			<cfcase value="3">
				<!--- extra compression can screw up a few little pieces of HTML, doh --->
				<cfset result = reReplace( result, "[[:space:]]{2,}", " ", "all" )>
				<cfset result = replace( result, "> <", "><", "all" )>
				<cfset result = reReplace( result, "<!--[^>]+>", "", "all" )>
			</cfcase>
			<cfcase value="2">
				<cfset result = reReplace( result, "[[:space:]]{2,}", chr( 13 ), "all" )>
			</cfcase>
			<cfcase value="1">
				<!--- only compresses after a line break --->
				<cfset result = reReplace( result, "(" & chr( 10 ) & "|" & chr( 13 ) & ")+[[:space:]]{2,}", chr( 13 ), "all" )>
			</cfcase>
		</cfswitch>
		
		<cfreturn result>
		
	</cffunction>
	
	
	<!--- --------------------------------------------------------------------------------------- ----
		
		Blog Entry:
		Graceful ColdFusion Timeout Disaster Recovery (Thanks Barney Boisvert)
		
		Author:
		Ben Nadel / Kinky Solutions
		
		Link:
		http://www.bennadel.com/index.cfm?dax=blog:916.view
		
		Date Posted:
		Aug 20, 2007 at 7:00 AM
		
		Purpose: get the current page request timeout
		
	---- --------------------------------------------------------------------------------------- --->

	<cffunction name="GetRequestTimeout" access="public" returntype="numeric" output="false" hint="Returns the current request timeout for the current page request.">
	 
		<!--- Define the local scope. --->
		<cfset var LOCAL = StructNew() />
	 
		<!--- Get the request monitor. --->
		<cfset LOCAL.RequestMonitor = CreateObject(
			"java",
			"coldfusion.runtime.RequestMonitor"
			) />
	 
		<!--- Return the current request timeout. --->
		<cfreturn LOCAL.RequestMonitor.GetRequestTimeout() />
		
	</cffunction>
	

	<!---
		Get the plural version of a word
		
		Follow codeigniter logic of checking
	--->
	
	<cffunction name="plural" access="public" returntype="string" output="false" hint="Return the plural version of a word">
		<cfargument name="word" type="string" required="yes" hint="The word to be checked">		
		<cfset var result = "">
		<cfset var thisword = lcase(trim(arguments.word))>
		<cfset var end = right(thisword, 1)>
		<cfset var end2 = right(thisword, 2)>
		
		<!--- End with a "y"? --->
		<cfif end eq "y">
			<!--- Preceeded by a vowel? --->
			<cfif listFind("a,e,i,o,u", left(right(thisword, 2), 1))>
				<cfset result = thisword & "s">
			<cfelse>
				<cfset result = mid(thisword, 1, len(thisword) - 1) & "ies">
			</cfif>
		
		<!--- End with s? --->
		<cfelseif end eq "s" OR end2 eq "sh">
			<cfset result = thisword & "es">
			
		<!--- Normal --->
		<cfelse>
			<cfset result = thisword & "s">
		</cfif>
		
		<cfreturn result>

	</cffunction>
	 

	<!---
		Extend the feature of Regular Expression function reMatch to also return the match position, 
		length and the content following it (good for extracting content based on heading format)
	--->
	
	<cffunction name="reMatchWithContent" displayname="reMatchWithContent" access="public" returntype="array" output="false">
		<cfargument name="regex" type="string" required="yes" hint="The regular expression to search for" />
		<cfargument name="string" type="string" required="yes" hint="The string to search in" />
		<cfargument name="endOfContent" type="string" required="no" default="" hint="The string that marks the end of the final content" />
		<cfset var result = ArrayNew(1)>
		<cfset var matches = REMatch(arguments.regex, arguments.string)>
		<cfset var startPos = "">
		<cfset var endPos = "">
		<cfset var counter = "">
		<cfset var thisMatch = "">
		<cfset var thisResult = "">
		<cfset var content = "">
		<cfset var contentStartPos = "">
		<cfset var contentLength = "">
		
		<cfif arrayLen(matches)>
			<cfset startPos = 1>
			<cfloop from="1" to="#arrayLen(matches)#" index="counter">
				<cfset thisMatch = matches[counter]>
				<cfset startPos = find(thisMatch, arguments.string, startPos)>
				
				<!--- Add this to the result array --->
				<cfset thisResult = StructNew()>	
				<cfset thisResult.match = thisMatch>
				<cfset thisResult.pos = startPos>
				<cfset thisResult.len = len(thisMatch)>
				
				<!--- Extract content between this match and the previous match --->
				<cfif counter gt 1>
					<cfset contentStartPos = result[counter - 1].pos + result[counter - 1].len>
					<cfset contentLength = thisResult.pos - contentStartPos>
					<cfset content = mid(arguments.string, contentStartPos, contentLength)>
					<cfset result[counter - 1].followingContent = content>
				</cfif>
				
				<cfset arrayAppend(result, thisResult)>
		
				<!--- Move up start position to prepare for next match --->
				<cfset startPos = startPos + len(thisMatch)>
			</cfloop>
			
			<!--- Extract content for the last match --->
			<cfset contentStartPos = thisResult.pos + thisResult.len>
			
			<!--- Get the end of content position --->
			<cfset endPos = 0>
			<cfif len(arguments.endOfContent)>
				<cfset endPos = findNoCase(arguments.endOfContent, arguments.string, contentStartPos)>
			</cfif>
			
			<!--- Get the content length --->
			<cfif endPos gt 0>
				<cfset contentLength = endPos - contentStartPos>
			<cfelse>
				<cfset contentLength = len(arguments.string)>
			</cfif>
			<cfset content = mid(arguments.string, contentStartPos, contentLength)>
			<cfset result[counter - 1].followingContent = content>
		</cfif>
		
		<cfreturn result>
		
	</cffunction>


	<!--- Extract content based on the start and end flags --->
	<cffunction name="extractContent" displayname="extractContent" access="public" returntype="array" output="false">
		<cfargument name="string" type="string" required="yes" hint="The string to search in" />
		<cfargument name="startFlag" type="string" required="yes" hint="The string that marks the start of searching" />
		<cfargument name="endFlag" type="string" required="yes" hint="The string that marks the end of searching" />
		<cfset var result = ArrayNew(1)>
		
		<!--- Find the first instance --->
		<cfset var startPos = 1>
		<cfset var found = reFindNoCase("#arguments.startFlag#(.*?)#arguments.endFlag#", arguments.string, startPos, true)>
		
		<cfloop condition="found.pos[1] gt 0">
			<!--- Extract the content --->
			<cfset found.match = ArrayNew(1)>
			<cfset found.match[1] = mid(arguments.string, found.pos[1], found.len[1])>
			<cfset found.match[2] = mid(arguments.string, found.pos[2], found.len[2])>
			
			<!--- Store the result --->
			<cfset arrayAppend(result, found)>
	
			<!--- Continue searching --->
			<cfset startPos = found.pos[1] + found.len[1]>
			<cfset found = reFindNoCase("#arguments.startFlag#(.*?)#arguments.endFlag#", arguments.string, startPos, true)>
		</cfloop>
		
		<cfreturn result>
				
	</cffunction>
	

	<!--- Create a url friendly version of a string --->
	<cffunction name="createURLFriendly" displayname="createURLFriendly" access="public" returntype="string" output="false">
		<cfargument name="string" type="string" required="yes" hint="The original string to start with">
		<cfset var result = trim(arguments.string)>
		
		<cfset result = reReplace(result, "([^\w-_]|\s)+", "-", "ALL")>
		<cfset result = reReplace(result, "[_]+", "-", "ALL")>
		<cfset result = reReplace(result, "[-]{2,}", "-", "ALL")>
		<cfset result = application.core.trimChar(result, "-")>
		
		<cfreturn result>
	
	</cffunction>
	
	
	<!--- Extract a string into an array of substrings --->
	<cffunction name="extract" access="public" returntype="array" output="false">
		<cfargument name="string" type="string" required="yes" hint="The string to search in" />
		<cfargument name="regExp" type="string" required="yes" hint="The regular expression used to search" />
		<cfset var result = ArrayNew(1)>
		<cfset var substrings = "">
		<cfset var found = "">
		<cfset var content = "">
		<cfset var counter = "">
		<cfset var thisMatch = "">

		<!--- Find all matches --->		
		<cfset var matches = ReMatch(arguments.regExp, arguments.string)>
		
		<!--- For each match, extract the substring --->
		<cfloop array="#matches#" index="thisMatch">
			<cfset substrings = ArrayNew(1)>
			<cfset found = reFindNoCase(arguments.regExp, thisMatch, 1, true)>

			<!--- Store the results --->
			<cfloop from="2" to="#arrayLen(found.pos)#" index="counter">
				<cfset content = mid(thisMatch, found.pos[counter], found.len[counter])>
				<cfset arrayAppend(substrings, content)>
			</cfloop>
			
			<cfset arrayAppend(result, substrings)>
		</cfloop>
		
		<cfreturn result>
		
	</cffunction>


	<!--- Get attributes inside a tag --->
	<cffunction name="getTagAttributes" access="public" returntype="struct" output="false">
		<cfargument name="tag" type="string" required="yes" hint="The tag to search in" />
		<cfset var result = StructNew()>
		<cfset var pair = "">
	
		<cfset var attributePairs = This.extract(arguments.tag, '[ |"]([^=]+)="([^"]+)"')>
		<cfloop array="#attributePairs#" index="pair">
			<cfset result[pair[1]] = pair[2]>
		</cfloop>
		
		<cfreturn result>
	
	</cffunction>


	<!--- Extract tags --->
	<cffunction name="extractTags" access="public" returntype="array" output="false">
		<cfargument name="string" type="string" required="yes" hint="The string to search in" />
		<cfargument name="tagName" type="string" required="yes" hint="The tag name" />
		<cfset var result = ArrayNew(1)>
		<cfset var t = "">
		<cfset var thisTag = "">
		
		<!--- Get tags and attributes --->
		<cfset var tags = application.utils.extract(arguments.string, '(<#arguments.tagName# [^>]+>)')>
		<cfloop array="#tags#" index="t">
			<cfset thisTag = StructNew()>
			<cfset thisTag.tag = t>
			<cfset thisTag.attributes = application.utils.getTagAttributes(t[1])>
			
			<cfset arrayAppend(result, thisTag)>
		</cfloop>
		
		<cfreturn result>
	
	</cffunction>
	
	
	<!--- Create a random number with specified number of digits --->
	<cffunction name="createRandomNumber" access="public" returntype="numeric" output="false">
		
		<cfargument name="digitsNum" type="numeric" required="yes" hint="The number of digits to be generated">
		<cfset var result = "">
		<cfset var local = StructNew()>
		
		<cfloop from="1" to="#val(arguments.digitsNum)#" index="local.index">
			<!--- The first digit cannot be 0 --->
			<cfif local.index eq 1>
				<cfset local.thisDigit = randRange(1, 9)>
			<cfelse>
				<cfset local.thisDigit = randRange(0, 9)>
			</cfif>
			
			<cfset result = result & local.thisDigit>
		</cfloop>
		
		<cfreturn result>		
		
	</cffunction>


	<!---
		Replace old HTML invalid ascii characters characters (mostly come from MS) with valid ascii characters
		The purpose of this function is to avoid funny characters showing up, especially used in feed.
		The replacement characters clearly lose its meanings and do not reflect the original character. But
		they were the closet ones that I could find.
	--->
	<cffunction name="replaceInvalidASCIICharacters" access="public" returntype="string" output="false">
		<cfargument name="string" type="string" required="yes" hint="The string in which characters are replaced" />
		<cfset var local = StructNew()>
		<cfset var result = "">

		<!--- The list of those characters that need to be escaped are taken from here: http://www.ascii.cl/htmlcodes.htm --->
		<!--- Get the list of old HTML characters that need to be replaced --->
		<cfset local.toConvert = StructNew()>
		
		<cfset local.toConvert[chr(338)] = "OE">
		<cfset local.toConvert[chr(339)] = "oe">
		<cfset local.toConvert[chr(352)] = "S">
		<cfset local.toConvert[chr(353)] = "s">
		<cfset local.toConvert[chr(376)] = "Y">
		<cfset local.toConvert[chr(402)] = "f">

		<cfset local.toConvert[chr(8211)] = "-">
		<cfset local.toConvert[chr(8212)] = "-">
		<cfset local.toConvert[chr(8216)] = "'">
		<cfset local.toConvert[chr(8217)] = "'">
		<cfset local.toConvert[chr(8218)] = "'">
		<cfset local.toConvert[chr(8220)] = '"'>
		<cfset local.toConvert[chr(8221)] = '"'>
		<cfset local.toConvert[chr(8222)] = ",,">
		<cfset local.toConvert[chr(8224)] = "|">
		<cfset local.toConvert[chr(8225)] = "|">
		<cfset local.toConvert[chr(8226)] = "*">
		<cfset local.toConvert[chr(8230)] = "...">
		<cfset local.toConvert[chr(8240)] = "%%">
		<cfset local.toConvert[chr(8364)] = "&euro;">
		<cfset local.toConvert[chr(8482)] = "TM">
		
		<!--- Replace the invalid characters one by one --->
		<cfset result = arguments.string>
		<cfloop collection="#local.toConvert#" item="local.fromCharacter">
			<cfset result = replace(result, local.fromCharacter, local.toConvert[local.fromCharacter], "ALL")>
		</cfloop>

		<cfreturn result>

	</cffunction>
</cfcomponent>